package cn.jhz.learn.blog.config.security;

import cn.jhz.learn.blog.component.shiro.JwtCredentialsMatcher;
import cn.jhz.learn.blog.component.shiro.MultiRealmAuthenticator;
import cn.jhz.learn.blog.component.shiro.filter.JwtAnonymousFilter;
import cn.jhz.learn.blog.component.shiro.filter.JwtAuthenticationFilter;
import cn.jhz.learn.blog.component.shiro.realm.JwtRealm;
import cn.jhz.learn.blog.component.shiro.realm.SimpleRealm;
import cn.jhz.learn.blog.service.security.permission.PermissionService;
import cn.jhz.learn.blog.service.security.permission.RoleService;
import cn.jhz.learn.blog.service.security.permission.UserService;

import com.google.common.collect.Lists;

import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authc.pam.AuthenticationStrategy;
import org.apache.shiro.authc.pam.FirstSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.mgt.SessionStorageEvaluator;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;

import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;


@Configuration
public class SecurityConfig {


    /**
     * 交由 Spring 来自动地管理 Shiro-Bean 的生命周期
     */
    @Bean
    public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 为 Spring-Bean 开启对 Shiro 注解的支持
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * spring自动代理
     * @return
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator app = new DefaultAdvisorAutoProxyCreator();
        app.setProxyTargetClass(true);
        return app;
    }

    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter(){
        return new JwtAuthenticationFilter();
    }

    @Bean
    public JwtAnonymousFilter jwtAnonymousFilter(){
        return new JwtAnonymousFilter();
    }

    @Bean
    JwtRealm jwtRealm(UserService userService, PermissionService permissionService, RoleService roleService){
        JwtRealm jwtRealm = new JwtRealm(userService, permissionService, roleService);

        CredentialsMatcher credentialsMatcher = new JwtCredentialsMatcher();
        jwtRealm.setCredentialsMatcher(credentialsMatcher);

        return  jwtRealm;
    }

    @Bean
    public SimpleRealm simpleRealm(UserService userService, PermissionService permissionService, RoleService roleService){
        SimpleRealm realm = new SimpleRealm(userService, permissionService, roleService);
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(Sha256Hash.ALGORITHM_NAME);

        matcher.setHashIterations(3);
        matcher.setStoredCredentialsHexEncoded(false);
        realm.setCredentialsMatcher(matcher);

        return realm;
    }

    /**
     * 配置 ModularRealmAuthenticator
     */
    @Bean
    public ModularRealmAuthenticator authenticator() {
        ModularRealmAuthenticator authenticator = new MultiRealmAuthenticator();
        // 设置多 Realm的认证策略，默认 AtLeastOneSuccessfulStrategy
        AuthenticationStrategy strategy = new FirstSuccessfulStrategy();
        authenticator.setAuthenticationStrategy(strategy);
        return authenticator;
    }

    /**
     * 禁用session, 不保存用户登录状态。保证每次请求都重新认证
     */
    @Bean
    protected SessionStorageEvaluator sessionStorageEvaluator() {
        DefaultSessionStorageEvaluator sessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        sessionStorageEvaluator.setSessionStorageEnabled(false);
        return sessionStorageEvaluator;
    }

    @Bean
    public SecurityManager securityManager(SimpleRealm simpleRealm, JwtRealm jwtRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        securityManager.setAuthenticator(authenticator());
        securityManager.setRealms(Lists.newArrayList(simpleRealm, jwtRealm));

        /*关闭shiro自带的session*/
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        subjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator());
        securityManager.setSubjectDAO(subjectDAO);

        return securityManager;
    }

    /**
     * 不向 Spring容器中注册 JwtFilter Bean，防止 Spring 将 JwtFilter 注册为全局过滤器
     * 全局过滤器会对所有请求进行拦截，而本例中只需要拦截除 /login 和 /logout 外的请求
     * 另一种简单做法是：直接去掉 jwtFilter()上的 @Bean 注解
     */
//    @Bean
//    public FilterRegistrationBean<Filter> registration(JwtAuthenticationFilter filter) {
//        FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<Filter>(filter);
//        registration.setEnabled(false);
//        return registration;
//    }

    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);

        // 添加 jwt 专用过滤器，拦截除 /login 和 /logout 外的请求
        Map<String, Filter> filterMap = new LinkedHashMap<>();
        filterMap.put("jwtAuthc", jwtAuthenticationFilter());
        filterMap.put("jwtAnon", jwtAnonymousFilter());
        shiroFilter.setFilters(filterMap);

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/api/public/core/**", "jwtAnon");
        filterChainDefinitionMap.put("/api/auth/**", "jwtAuthc");
        shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return shiroFilter;
    }
}
